// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "bootfs.h"

#include <string.h>
#include <zircon/boot/bootfs.h>
#include <zircon/syscalls.h>

#include "util.h"

void bootfs_mount(zx_handle_t vmar, zx_handle_t log, zx_handle_t vmo, struct bootfs* fs) {
  uint64_t size;
  zx_status_t status = zx_vmo_get_size(vmo, &size);
  check(log, status, "zx_vmo_get_size failed on bootfs vmo\n");
  uintptr_t addr = 0;
  status = zx_vmar_map(vmar, ZX_VM_PERM_READ, 0, vmo, 0, size, &addr);
  check(log, status, "zx_vmar_map failed on bootfs vmo\n");
  fs->contents = reinterpret_cast<const std::byte*>(addr);
  fs->len = size;
  status = zx_handle_duplicate(
      vmo, ZX_RIGHT_READ | ZX_RIGHT_MAP | ZX_RIGHTS_BASIC | ZX_RIGHT_GET_PROPERTY, &fs->vmo);
  check(log, status, "zx_handle_duplicate failed on bootfs VMO handle\n");
}

void bootfs_unmount(zx_handle_t vmar, zx_handle_t log, struct bootfs* fs) {
  zx_status_t status = zx_vmar_unmap(vmar, (uintptr_t)fs->contents, fs->len);
  check(log, status, "zx_vmar_unmap failed\n");
  status = zx_handle_close(fs->vmo);
  check(log, status, "zx_handle_close failed\n");
}

static const zbi_bootfs_dirent_t* bootfs_search(zx_handle_t log, struct bootfs* fs,
                                                const char* root_prefix, const char* filename) {
  const std::byte* p = fs->contents;

  if (fs->len < sizeof(zbi_bootfs_header_t))
    fail(log, "bootfs is too small");

  const zbi_bootfs_header_t* hdr = reinterpret_cast<const zbi_bootfs_header_t*>(p);
  if ((hdr->magic != ZBI_BOOTFS_MAGIC) || (hdr->dirsize > fs->len))
    fail(log, "bootfs bad magic or size");

  size_t prefix_len = strlen(root_prefix);
  size_t filename_len = strlen(filename) + 1;

  p += sizeof(zbi_bootfs_header_t);
  size_t avail = hdr->dirsize;

  while (avail > sizeof(zbi_bootfs_dirent_t)) {
    auto e = reinterpret_cast<const zbi_bootfs_dirent_t*>(p);

    size_t sz = ZBI_BOOTFS_DIRENT_SIZE(e->name_len);
    if ((e->name_len < 1) || (sz > avail))
      fail(log, "bootfs has bogus namelen in header");

    if (e->name_len == prefix_len + filename_len && !memcmp(e->name, root_prefix, prefix_len) &&
        !memcmp(&e->name[prefix_len], filename, filename_len)) {
      return e;
    }

    p += sz;
    avail -= sz;
  }

  return NULL;
}

zx_handle_t bootfs_open(zx_handle_t log, const char* purpose, struct bootfs* fs,
                        const char* root_prefix, const char* filename) {
  printl(log, "searching bootfs for '%s%s'", root_prefix, filename);

  const zbi_bootfs_dirent_t* e = bootfs_search(log, fs, root_prefix, filename);
  if (e == NULL) {
    printl(log, "file not found");
    return ZX_HANDLE_INVALID;
  }
  if (e->data_off > fs->len)
    fail(log, "bogus offset in bootfs header!");
  if (fs->len - e->data_off < e->data_len)
    fail(log, "bogus size in bootfs header!");

  // Clone a private copy of the file's subset of the bootfs VMO.
  // TODO(mcgrathr): Create a plain read-only clone when the feature
  // is implemented in the VM.
  zx_handle_t vmo;
  zx_status_t status =
      zx_vmo_create_child(fs->vmo, ZX_VMO_CHILD_COPY_ON_WRITE, e->data_off, e->data_len, &vmo);
  if (status != ZX_OK)
    fail(log, "zx_vmo_create_child failed: %d", status);

  zx_object_set_property(vmo, ZX_PROP_NAME, filename, strlen(filename));

  // Drop unnecessary ZX_RIGHT_WRITE rights.
  // TODO(mcgrathr): Should be superfluous with read-only zx_vmo_create_child.
  status = zx_handle_replace(
      vmo, ZX_RIGHT_READ | ZX_RIGHT_MAP | ZX_RIGHTS_BASIC | ZX_RIGHT_GET_PROPERTY, &vmo);
  if (status != ZX_OK)
    fail(log, "zx_handle_replace failed: %d", status);

  // TODO(mdempsky): Restrict to bin/ and lib/.
  status = zx_vmo_replace_as_executable(vmo, ZX_HANDLE_INVALID, &vmo);
  if (status != ZX_OK)
    fail(log, "zx_vmo_replace_as_executable failed: %d", status);

  return vmo;
}
